/**
 *  @file FileStream.cpp
 */
#if defined(WIN32)
#   include <windows.h>
#elif defined(linux)
#   include <stdio.h>
#   include <sys/types.h>
#   include <sys/stat.h>
#   include <unistd.h>
#   include <fcntl.h>
#   include <string.h>
#endif
#include "../common/CppMemPool.h"
#include "../common/ObjPtrT.h"
#include "../common/UtilFuncT.h"
#include "../common/CppflibMain.h"
#include "../collections/BinBuf.h"
#include "../text/CFString.h"
#include "FileStream.h"
#include "IOException.h"
#include "__internal/WriteCharHelper.h"


namespace cppflib
{

namespace io
{

PRIVATE const int LINEBUFSIZE = 160;  ///< initial and subsequent amount of expansion of a line buffer

/*** various newline chars ***/
PRIVATE u8_t st_u8_lf[] = {'\n', '\0'};
PRIVATE u8_t st_u8_crlf[] = {'\r', '\n', '\0'};
PRIVATE u8_t st_u16le_lf[] = {'\n', '\0', '\0', '\0'};
PRIVATE u8_t st_u16le_crlf[] = {'\r', '\0', '\n', '\0', '\0', '\0'};
PRIVATE u8_t st_u16be_lf[] = {'\0', '\n', '\0', '\0'};
PRIVATE u8_t st_u16be_crlf[] = {'\0', '\r', '\0', '\n', '\0', '\0'};

/*** BOM for different encodnig ***/
PRIVATE u8_t st_u8_bom[] = {0xef, 0xbb, 0xbf};
PRIVATE u8_t st_u16le_bom[] = {0xff, 0xfe};
PRIVATE u8_t st_u16be_bom[] = {0xfe, 0xff};

/**
 *  To determine file text encoding by reading BOM
 */
FileStream::fileencoding_t __DetermineInputEncoding(ptrany_t hFile)
{
   FileStream::fileencoding_t enc = FileStream::ENC_NONE;

#if defined(WIN32)
   // less than 3 bytes, quit
   HANDLE hFileHandle = static_cast<HANDLE>(hFile);
   u32_t highSize = 0, lowSize = 0;
   lowSize = ::GetFileSize(hFileHandle, reinterpret_cast<LPDWORD>(&highSize));
   if (lowSize == INVALID_FILE_SIZE && cppflib::GetLastError() != 0) 
      return enc;

   u64_t fileSize = (static_cast<u64_t>(highSize) << 32) | (static_cast<u64_t>(lowSize) & U64N(0xffffffff));

   // read first 3 bytes
   u8_t buf[3] = {0, 0, 0};
   u32_t nrBytesRead = 0;
   if (!::ReadFile(hFileHandle, buf, 3, reinterpret_cast<LPDWORD>(&nrBytesRead), NULL))
      return enc;
   

   if (::memcmp(buf, st_u16le_bom, 2) == 0)
      enc = FileStream::ENC_UTF16LE;
   else if (::memcmp(buf, st_u16be_bom, 2) == 0)
      enc = FileStream::ENC_UTF16BE;
   else if (::memcmp(buf, st_u8_bom, 3) == 0)
      enc = FileStream::ENC_UTF8;

   // adjust file pointer
   if (enc == FileStream::ENC_NONE) {
      ::SetFilePointer(hFileHandle, 0, NULL, FILE_BEGIN);  
   }
   else if (enc == FileStream::ENC_UTF16LE || enc == FileStream::ENC_UTF16BE) {
      ::SetFilePointer(hFileHandle, 2, NULL, FILE_BEGIN);  
   }
   
#elif defined(linux)
   struct stat fileState;
   FILE *fp = reinterpret_cast<FILE *>(hFile);
   
   fstat(fileno(fp), &fileState);
   sz_t fileSize = static_cast<sz_t>(fileState.st_size);
   
   // read first 3 bytes
   u8_t buf[3] = {0, 0, 0};
   u32_t nrBytesRead = static_cast<u32_t>(fread(buf, sizeof(u8_t), 3, fp));

   if (::memcmp(buf, st_u16le_bom, 2) == 0)
      enc = FileStream::ENC_UTF16LE;
   else if (::memcmp(buf, st_u16be_bom, 2) == 0)
      enc = FileStream::ENC_UTF16BE;
   else if (::memcmp(buf, st_u8_bom, 3) == 0)
      enc = FileStream::ENC_UTF8;

   // adjust file pointer
   if (enc == FileStream::ENC_NONE) {
      fseeko(fp, 0, SEEK_SET);  
   }
   else if (enc == FileStream::ENC_UTF16LE || enc == FileStream::ENC_UTF16BE) {
      fseeko(fp, 2, SEEK_SET);
   }
#endif

   return enc;
}

FileStream::FileStream(void)
{
   fileHandle = NULL;
   pBinBuf = NULL;
}

/**
 *  Construct a file stream
 *
 *  @note exception is thrown if error occurs while opening file
 */
FileStream::FileStream(pcwstr_t szFilePath, fileattr_t openAttr)
{
   fileHandle = NULL;
   pBinBuf = NULL;


#if defined(WIN32)
   nlType = NL_CRLF;  // default on Windows

   // access
   u32_t desiredAccess = GENERIC_READ;
   if (openAttr == FileStream::FA_WRITE)
      desiredAccess = GENERIC_WRITE;
   else if (openAttr == FileStream::FA_APPEND)
      desiredAccess = FILE_APPEND_DATA;

   // share mode
   u32_t shareMode = FILE_SHARE_READ;

   // create mode
   u32_t creationDisposition = OPEN_ALWAYS;
   if (openAttr == FileStream::FA_WRITE)
      creationDisposition = CREATE_ALWAYS;
   else if (openAttr == FileStream::FA_READONLY || 
            openAttr == FileStream::FA_READ_DETECTENC)
      creationDisposition = OPEN_EXISTING;

   // flags
   u32_t flagsAndAttributes = FILE_ATTRIBUTE_NORMAL;

   HANDLE hFile = ::CreateFile(szFilePath, desiredAccess, shareMode, NULL, creationDisposition, 
                               flagsAndAttributes, NULL);
   if (hFile == INVALID_HANDLE_VALUE) {
      THROW_CTOR_EXCEP_CODE(IOException, _S("Open file error !"), cppflib::GetLastError());
   }

   fileHandle = static_cast<ptrany_t>(hFile);

#elif defined(linux)
   nlType = NL_LF;   // default on linux

   CFString szFile(szFilePath);
   ObjPtrT<char> szUTF8File(szFile.ToUtf8String());
   const char *mode = "r";
   if (openAttr == FileStream::FA_WRITE)
      mode = "w";
   else if (openAttr == FileStream::FA_APPEND)
      mode = "a";

   FILE *fp = fopen(szUTF8File, mode);
   if (!fp) {
      THROW_CTOR_EXCEP_CODE(IOException, _S("Open file error !"), cppflib::GetLastError());
   }

   fileHandle = reinterpret_cast<ptrany_t>(fp);

#endif
   
   this->openAttr = openAttr;
   outEnc = FileStream::ENC_UTF8; // default
   inEnc = (openAttr == FileStream::FA_READ_DETECTENC) ? __DetermineInputEncoding(this->fileHandle) : FileStream::ENC_NONE;
   SetOutputNewLine(this->nlType);
}

FileStream::~FileStream(void)
{
   Close();
}

void FileStream::ReleaseBeforeThrowing()
{
   if (fileHandle) {

#if defined(WIN32)
      ::CloseHandle(static_cast<HANDLE>(fileHandle));
#elif defined(linux)
      fclose(reinterpret_cast<FILE *>(fileHandle));
#endif
      fileHandle = NULL;
   }

   if (pBinBuf) {
      cppmempool::Release(pBinBuf);
      pBinBuf = NULL;
   }
}

/**
 *  Close file stream
 */
void FileStream::Close()
{
   ReleaseBeforeThrowing();
}

/**
 *  Flush any buffered data
 */
void FileStream::Flush()
{

#if defined(WIN32)
   ::FlushFileBuffers(static_cast<HANDLE>(fileHandle));
#elif defined(linux)
   fsync(fileno(reinterpret_cast<FILE *>(fileHandle)));
#endif

}

/**
 *  Read bytes from file
 *
 *  @return number of bytes read
 */
int FileStream::Read(collections::ArrayByte &buffer)
{
   return this->Read(buffer, 0, static_cast<int>(buffer.GetSize()));
}

/**
 *  Read bytes from file
 *
 *  @return number of bytes read
 */
int FileStream::Read(collections::ArrayByte &buffer, int offset, int count)
{
   if (count < 0 || offset < 0 || offset >= static_cast<int>(buffer.GetSize()))
      return 0;

   // use the smaller one
   count = cppflib::Min<int>(count, static_cast<int>(buffer.GetSize() - offset));

   u8_t * pBuf = buffer.GetRawPtr() + offset;
   u32_t nrBytesRead = 0;

#if defined(WIN32)
   if (!::ReadFile(static_cast<HANDLE>(fileHandle), pBuf, count, reinterpret_cast<LPDWORD>(&nrBytesRead), NULL))
      return 0;
#elif defined(linux)
   nrBytesRead = static_cast<u32_t>(fread(pBuf, sizeof(u8_t), count, reinterpret_cast<FILE *>(fileHandle)));
#endif

   
   return static_cast<int>(nrBytesRead);
}

/**
 *  Read a single byte
 *
 *  @return -1 if error
 *  @return byte read
 */
int FileStream::Read()
{
   u32_t nrBytesRead = 0;
   u8_t buf[1];

#if defined(WIN32)
   if (!::ReadFile(static_cast<HANDLE>(fileHandle), buf, 1, reinterpret_cast<LPDWORD>(&nrBytesRead), NULL) 
       || nrBytesRead == 0) {
      return -1;
   }

#elif defined(linux)
   nrBytesRead = static_cast<u32_t>(fread(buf, sizeof(u8_t), 1, reinterpret_cast<FILE *>(fileHandle)));
   if (nrBytesRead == 0)
      return -1;
#endif

   return (static_cast<int>(buf[0]) & 0xff);
}

/**
 *  Seek to a position within the file
 *  
 *  @return new pos after seek
 *  @return -1 if failed
 */
i64_t FileStream::Seek(i64_t offset, Stream::seekorigin_t origin)
{
#if defined(WIN32)
    u32_t lowOff = static_cast<u32_t>(offset & I64N(0xffffffff));
    u32_t highOff = static_cast<u32_t>((offset >> 32) & I64N(0xffffffff));
    u32_t moveMethod = (origin == S_BEGIN) ? FILE_BEGIN :
                               (origin == S_CUR) ? FILE_CURRENT : FILE_END;

    u32_t ret = ::SetFilePointer(static_cast<HANDLE>(fileHandle), static_cast<LONG>(lowOff), 
                                 highOff == 0 ? NULL : reinterpret_cast<PLONG>(&highOff), moveMethod); 
    if (ret == INVALID_SET_FILE_POINTER)
       return static_cast<i64_t>(-1);
    
    if (highOff == 0)
       return static_cast<i64_t>(ret & I64N(0xffffffff));

    return ((static_cast<i64_t>(highOff) << 32) | static_cast<i64_t>(ret & I64N(0xffffffff)));
#elif defined(linux)
    int moveMethod = (origin == S_BEGIN) ? SEEK_SET :
                               (origin == S_CUR) ? SEEK_CUR : SEEK_END;

    if (fseeko(reinterpret_cast<FILE *>(fileHandle), static_cast<off_t>(offset), moveMethod) != 0)
       return static_cast<i64_t>(-1);

    return static_cast<i64_t>(ftello(reinterpret_cast<FILE *>(fileHandle)));
#endif

}

/**
 *  Write bytes to file
 *
 *  @return number of bytes written
 */
int FileStream::Write(collections::ArrayByte &buffer)
{
   return this->Write(buffer, 0, static_cast<int>(buffer.GetSize()));
}

/**
 *  Write bytes to file
 *
 *  @return number of bytes written
 */
int FileStream::Write(collections::ArrayByte &buffer, int offset, int count)
{
   if (openAttr != FileStream::FA_WRITE && openAttr != FileStream::FA_APPEND)
      return 0;

   if (count < 0 || offset < 0 || offset >= static_cast<int>(buffer.GetSize()))
      return 0;

   // use the smaller one
   count = cppflib::Min<int>(count, static_cast<int>(buffer.GetSize() - offset));

   u8_t * pBuf = buffer.GetRawPtr() + offset;
   u32_t nrBytesWritten = 0;

#if defined(WIN32)
   
   if (!::WriteFile(static_cast<HANDLE>(fileHandle), pBuf, count, reinterpret_cast<LPDWORD>(&nrBytesWritten), NULL))
      return 0;

#elif defined(linux)
   nrBytesWritten = static_cast<u32_t>(fwrite(pBuf, sizeof(u8_t), count, reinterpret_cast<FILE *>(fileHandle)));
#endif
  
   return static_cast<int>(nrBytesWritten);
}

/**
 *  Write a single byte
 */
bool FileStream::Write(u8_t val)
{
   if (openAttr != FileStream::FA_WRITE && openAttr != FileStream::FA_APPEND)
      return false;
   
#if defined(WIN32)
   u32_t nrBytesWritten = 0;
   if (!::WriteFile(static_cast<HANDLE>(fileHandle), &val, 1, reinterpret_cast<LPDWORD>(&nrBytesWritten), NULL))
      return false;

#elif defined(linux)
   if (fwrite(&val, sizeof(u8_t), 1, reinterpret_cast<FILE *>(fileHandle)) != 1)
      return false;
#endif

   return true;

}

/**
 *  Lock a portion of file
 *
 *  @note this function return immediately no matter succeed or not
 */
bool FileStream::Lock(i64_t pos, i64_t length)
{
#if defined(WIN32)
   u32_t lowPos = static_cast<u32_t>(pos & U64N(0xffffffff));
   u32_t highPos = static_cast<u32_t>((pos >> 32) & U64N(0xffffffff));
   u32_t lowLen = static_cast<u32_t>(length & U64N(0xffffffff));
   u32_t highLen = static_cast<u32_t>((length >> 32) & U64N(0xffffffff));
   
   if (::LockFile(static_cast<HANDLE>(fileHandle), lowPos, highPos, lowLen, highLen))
      return true;

   return false;
#elif defined(linux)
   struct flock lockDes = {0};
   lockDes.l_type = (openAttr == FileStream::FA_READONLY || 
                     openAttr == FileStream::FA_READ_DETECTENC) ? F_RDLCK : F_WRLCK;
   lockDes.l_whence = SEEK_SET;
   lockDes.l_start = static_cast<off_t>(pos);
   lockDes.l_len = static_cast<off_t>(length);
   
   if (fcntl(fileno(reinterpret_cast<FILE *>(fileHandle)), F_SETLK, &lockDes) == 0)
      return true;

   return false;
#endif

}

/**
 *  Unlock a portion of file
 */
bool FileStream::Unlock(i64_t pos, i64_t length)
{
#if defined(WIN32)
   u32_t lowPos = static_cast<u32_t>(pos & U64N(0xffffffff));
   u32_t highPos = static_cast<u32_t>((pos >> 32) & U64N(0xffffffff));
   u32_t lowLen = static_cast<u32_t>(length & U64N(0xffffffff));
   u32_t highLen = static_cast<u32_t>((length >> 32) & U64N(0xffffffff));
   
   if (::UnlockFile(static_cast<HANDLE>(fileHandle), lowPos, highPos, lowLen, highLen))
      return true;

   return false;
#elif defined(linux)
   struct flock lockDes = {0};
   lockDes.l_type = F_UNLCK;
   lockDes.l_whence = SEEK_SET;
   lockDes.l_start = static_cast<off_t>(pos);
   lockDes.l_len = static_cast<off_t>(length);
   
   if (fcntl(fileno(reinterpret_cast<FILE *>(fileHandle)), F_SETLK, &lockDes) == 0)
      return true;

   return false;
#endif

}

/**
 *  Read a line of text until '\n' is encountered
 *
 *  @param [out] szStr -- string to hold the resultant line of string (not including '\r', '\n')
 *
 *  @note this method will only execute if the input encoding is
 *        UTF-8, UTF-16LE or UTF-16BE
 *
 *  @return true -- a line is read
 *  @return false -- no line is read
 */
bool FileStream::ReadLine(CFString &szStr)
{
   if (inEnc != FileStream::ENC_UTF8 && 
       inEnc != FileStream::ENC_UTF16LE && 
       inEnc != FileStream::ENC_UTF16BE)
      return false;

   if (!pBinBuf) {
      pBinBuf = new collections::BinBuf();
      cppmempool::Retain(pBinBuf);
      pBinBuf->Realloc(LINEBUFSIZE);
   }
   else {
      pBinBuf->SetValidLength(0);
   }


   u32_t nrBytesRead = 0;
   u8_t * pLineEnd = NULL;  // this will point to '\n' or last char in a buffer
   u8_t * pRawBuf = pBinBuf->GetRawBuf();
   u16_t NEWLINECHAR, CARRIAGERETURNCHAR;
   if ((inEnc == FileStream::ENC_UTF16LE && cppflib::IsLittleEndian()) ||
       (inEnc == FileStream::ENC_UTF16BE && !cppflib::IsLittleEndian()) ) {
      NEWLINECHAR = 0x000a;
      CARRIAGERETURNCHAR = 0x000d;
   }
   else {
      // characters when in different endians
      NEWLINECHAR = 0x0a00;
      CARRIAGERETURNCHAR = 0x0d00;
   }
   
   while (true) {
      //
      //  Read LINEBUFSIZE amount of bytes each time and find '\n' if exists
      //

      nrBytesRead = 0;
#if defined(WIN32)
      ::ReadFile(static_cast<HANDLE>(fileHandle), pRawBuf, LINEBUFSIZE, reinterpret_cast<LPDWORD>(&nrBytesRead), NULL);  
#elif defined(linux)
      nrBytesRead = static_cast<u32_t>(fread(pRawBuf, sizeof(u8_t), LINEBUFSIZE, reinterpret_cast<FILE *>(fileHandle)));
#endif

      pBinBuf->SetValidLength(pBinBuf->GetValidLength() + nrBytesRead);

      if (inEnc == FileStream::ENC_UTF8) {
         u8_t * pTra = pRawBuf;
         u32_t i = 0;
         for (i = 0; i < nrBytesRead; ++i) {
            if (*pTra == '\n')
               break;
            ++pTra;
         }
         
         if (i < nrBytesRead)
            pLineEnd = pTra;
      }
      else if (inEnc == FileStream::ENC_UTF16LE ||
               inEnc == FileStream::ENC_UTF16BE) {

         if (nrBytesRead % 2 != 0) // to ensure comparing 2 bytes each
            --nrBytesRead;

         u16_t * pTra = reinterpret_cast<u16_t*>(pRawBuf);
         u32_t i = 0;
         for (i = 0; i < nrBytesRead; i += 2) {
            if (*pTra == NEWLINECHAR)
               break;
            ++pTra;
         }

         if (i < nrBytesRead)
            pLineEnd = reinterpret_cast<u8_t*>(pTra);
      }
      
      // end of line found
      if (pLineEnd != NULL)
         break;

      if (nrBytesRead == 0) {
         pLineEnd = pRawBuf;
         break;
      }
      else if (nrBytesRead < LINEBUFSIZE) { 
         // end of file reached, assume it is a complete line
         pLineEnd = pRawBuf + nrBytesRead - ((inEnc == FileStream::ENC_UTF8) ? 1 : 2);
         break;
      }

      // can't find "newline", continue to read (expand buffer required)
      u32_t bufOffset = pRawBuf - pBinBuf->GetRawBuf();
      pBinBuf->Realloc(pBinBuf->GetValidLength() + LINEBUFSIZE);
      pRawBuf = pBinBuf->GetRawBuf() + bufOffset + LINEBUFSIZE;
      
   } // end of while

   // nothing is read
   if (pRawBuf == pLineEnd)
      return false;

   // reset file pointer to start of line (no need if end of file is reached already)
   if (pLineEnd < pBinBuf->GetValidEnd()) {

      i64_t rewindOff = static_cast<i64_t>(pBinBuf->GetValidEnd() - pLineEnd);
      rewindOff -= (inEnc == FileStream::ENC_UTF8) ? 1 : 2;
      Seek(-rewindOff, FileStream::S_CUR);
   }

   // put buffer into string
   szStr.Delete(0, szStr.GetLength());
   if (inEnc == FileStream::ENC_UTF8) {

      // mark zero at the end
      if (*pLineEnd == '\n') {
         if (pLineEnd - 1 >= pRawBuf && *(pLineEnd - 1) == '\r')
            *(pLineEnd - 1) = '\0';
         else
            *pLineEnd = '\0';
      }
      else {
         if (pLineEnd + 1 >= pBinBuf->GetBufferEnd())
            pBinBuf->AppendData(reinterpret_cast<const u8_t*>("\0"), 1);
         else
            *(pLineEnd + 1) = '\0';
      }

      szStr += reinterpret_cast<const char *>(pBinBuf->GetRawBuf());
   }
   else if (inEnc == FileStream::ENC_UTF16LE ||
            inEnc == FileStream::ENC_UTF16BE) {
      // mark zero at the end
      u16_t * pTra = (u16_t*)pLineEnd;
      if (*pTra == NEWLINECHAR) {
         if (pLineEnd - 2 >= pRawBuf && *(pTra - 1) == CARRIAGERETURNCHAR)
            *(pTra - 1) = 0x0000;
         else
            *pTra = 0x0000;
      }
      else {
         // for safety reason, advance 3 bytes to test rather than 2
         if (pLineEnd + 3 >= pBinBuf->GetBufferEnd())
            pBinBuf->AppendData(reinterpret_cast<const u8_t*>("\0\0\0"), 3);
         else
            *(pTra + 1) = 0x0000;
      }

      // must be the same endian, though I don't have a big endian CPU
      if ((inEnc == FileStream::ENC_UTF16LE && !cppflib::IsLittleEndian()) || 
          (inEnc == FileStream::ENC_UTF16BE && cppflib::IsLittleEndian())) {
         
         // reverse endianess of each character
         u8_t * pFirstByte = pBinBuf->GetRawBuf();
         u8_t * pSecondByte = pFirstByte + 1;
         u8_t temp;
         while (pFirstByte <= pLineEnd) {
            temp = *pFirstByte;
            *pFirstByte = *pSecondByte;
            *pSecondByte = temp;
            pFirstByte += 2;
            pSecondByte += 2;
         }
      }

      szStr += reinterpret_cast<pcwstr_t>(pBinBuf->GetRawBuf());
   }
      
   return true;
}

/**
 *  Write string to file
 * 
 *  @note this method will only execute if the input encoding is
 *        UTF-8, UTF-16LE or UTF-16BE
 *
 *  @param [in] isEndWithNewline -- true will append newline char at the end
 */
void FileStream::WriteString(CFString &szStr, bool isEndWithNewline)
{
   if (openAttr != FileStream::FA_WRITE && openAttr != FileStream::FA_APPEND)
      return;

   if (outEnc != FileStream::ENC_UTF8 && 
       outEnc != FileStream::ENC_UTF16LE && 
       outEnc != FileStream::ENC_UTF16BE)
      return;

   if (!pBinBuf) {
      pBinBuf = new collections::BinBuf();
      cppmempool::Retain(pBinBuf);
   }

   //
   //  transfer characters in szStr to pBinBuf first
   //
   if (outEnc == FileStream::ENC_UTF8) {
      WriteCharsToBuffer(static_cast<pcwstr_t>(szStr), szStr.GetLength(), *pBinBuf, text::CS_UTF8);
      if (isEndWithNewline)
         pBinBuf->AppendData(outNewLine, CFString::Strlen(reinterpret_cast<const char*>(outNewLine)));
   }
   else if (outEnc == FileStream::ENC_UTF16LE || outEnc == FileStream::ENC_UTF16BE) {
      WriteCharsToBuffer(static_cast<pcwstr_t>(szStr), szStr.GetLength(), *pBinBuf, 
                          (outEnc == FileStream::ENC_UTF16LE) ? text::CS_UTF16LE : text::CS_UTF16BE);
      if (isEndWithNewline)
         pBinBuf->AppendData(outNewLine, CFString::WStrlen(reinterpret_cast<pcwstr_t>(outNewLine)) * sizeof(wc_t));
   }
  
   if (pBinBuf->GetValidLength() > 0) {
#if defined(WIN32)
      u32_t nrBytesWritten = 0;
      ::WriteFile(static_cast<HANDLE>(fileHandle), pBinBuf->GetRawBuf(), pBinBuf->GetValidLength(), reinterpret_cast<LPDWORD>(&nrBytesWritten), NULL);
#elif defined(linux)
      fwrite(pBinBuf->GetRawBuf(), sizeof(u8_t), pBinBuf->GetValidLength(), reinterpret_cast<FILE *>(fileHandle));
#endif
   }

}

/**
 *  Write string only
 */
void FileStream::Write(CFString &szStr)
{
   WriteString(szStr, false);
}

/**
 *  Write string with new line appended
 */
void FileStream::WriteLine(CFString &szStr)
{
   WriteString(szStr, true);
}

/**
 *  Write byte order mark
 */
void FileStream::WriteBOM()
{
   if (openAttr != FileStream::FA_WRITE && openAttr != FileStream::FA_APPEND)
      return;

   if (outEnc != FileStream::ENC_UTF8 && 
       outEnc != FileStream::ENC_UTF16LE && 
       outEnc != FileStream::ENC_UTF16BE)
      return;

   collections::ArrayByte bomBuf(4);
   if (outEnc == FileStream::ENC_UTF8) {
      bomBuf[0] = st_u8_bom[0];
      bomBuf[1] = st_u8_bom[1];
      bomBuf[2] = st_u8_bom[2];
   }
   else if (outEnc == FileStream::ENC_UTF16LE) {
      bomBuf[0] = st_u16le_bom[0];
      bomBuf[1] = st_u16le_bom[1];
   }
   else if (outEnc == FileStream::ENC_UTF16BE) {
      bomBuf[0] = st_u16be_bom[0];
      bomBuf[1] = st_u16be_bom[1];
   }

   Write(bomBuf, 0, static_cast<int>(CFString::Strlen(reinterpret_cast<const char*>(bomBuf.GetRawPtr()))));
}

FileStream::fileencoding_t FileStream::GetInputEncoding()
{
   return this->inEnc;
}

FileStream::fileencoding_t FileStream::GetOutputEncoding()
{
   return this->outEnc;
}

void FileStream::SetInputEncoding(FileStream::fileencoding_t enc)
{
   this->inEnc = enc;
}

void FileStream::SetOutputEncoding(FileStream::fileencoding_t enc)
{
   this->outEnc = enc;
   SetOutputNewLine(this->nlType);
}

/**
 *  Set newline type and actual output newline characters
 */
void FileStream::SetOutputNewLine(FileStream::newlinetype_t nlType)
{
   this->nlType = nlType;

   if (nlType == NL_LF) {
      switch (this->outEnc) {
         case FileStream::ENC_UTF8:
            outNewLine = st_u8_lf; break;
         case FileStream::ENC_UTF16LE:
            outNewLine = st_u16le_lf; break;
         case FileStream::ENC_UTF16BE:
            outNewLine = st_u16be_lf; break;
         default:
            outNewLine = st_u8_lf; break;
      }
   }
   else if (nlType == NL_CRLF) {
      switch (this->outEnc) {
         case FileStream::ENC_UTF8:
            outNewLine = st_u8_crlf; break;
         case FileStream::ENC_UTF16LE:
            outNewLine = st_u16le_crlf; break;
         case FileStream::ENC_UTF16BE:
            outNewLine = st_u16be_crlf; break;
         default:
            outNewLine = st_u8_crlf; break;
      }
   }
      
}

/**
 *  Get size of current opened file
 */
u64_t FileStream::GetFileSize()
{
#if defined(WIN32)
   u32_t highSize = 0, lowSize = 0;
   lowSize = ::GetFileSize(static_cast<HANDLE>(fileHandle), reinterpret_cast<LPDWORD>(&highSize));
   if (lowSize == INVALID_FILE_SIZE && cppflib::GetLastError() != 0) 
      return 0;

   return (static_cast<u64_t>(highSize) << 32) | (static_cast<u64_t>(lowSize) & U64N(0xffffffff));
#elif defined(linux)
   struct stat fileState;
   fstat(fileno(reinterpret_cast<FILE *>(fileHandle)), &fileState);
   return static_cast<u64_t>(fileState.st_size);
#endif
}


} // end of namespace io

} // end of namespace cppflib

